﻿using System;
using System.Linq.Expressions;

namespace Tszy.Unlimited.Data.AdoNet.Sql.Components.Visitors
{
    /// <summary>
    ///
    /// </summary>
    public interface IEvaluator
    {
        /// <summary>
        ///
        /// </summary>
        /// <param name="exp"></param>
        /// <returns></returns>
        object Eval(Expression exp);
    }

    /// <summary>
    ///
    /// </summary>
    public class CacheEvaluator : IEvaluator
    {
        /// <summary>
        ///
        /// </summary>
        private static readonly IExpressionCache s_cache = new HashedListCache();

        /// <summary>
        ///
        /// </summary>
        private readonly WeakTypeDelegateGenerator m_delegateGenerator = new();

        /// <summary>
        ///
        /// </summary>
        private readonly ConstantExtractor m_constantExtrator = new();

        /// <summary>
        ///
        /// </summary>
        private readonly IExpressionCache m_cache;

        /// <summary>
        ///
        /// </summary>
        private readonly Func<Expression, Delegate> m_creatorDelegate;

        /// <summary>
        ///
        /// </summary>
        private static readonly object _lockObject = new();

        /// <summary>
        ///
        /// </summary>
        public CacheEvaluator()
            : this(s_cache)
        { }

        /// <summary>
        ///
        /// </summary>
        /// <param name="cache"></param>
        public CacheEvaluator(IExpressionCache cache)
        {
            this.m_cache = cache;
            this.m_creatorDelegate = (key) => m_delegateGenerator.Generate(key);
        }

        /// <summary>
        ///
        /// </summary>
        /// <param name="exp"></param>
        /// <returns></returns>
        public object Eval(Expression exp)
        {
            if (exp.NodeType == ExpressionType.Constant)
            {
                return ((ConstantExpression)exp).Value;
            }

            lock (_lockObject)
            {
                var parameters = this.m_constantExtrator.Extract(exp);
                var func = this.m_cache.Get(parameters.Count.ToString(), exp, this.m_creatorDelegate);
                return func.DynamicInvoke(parameters.ToArray());
            }
        }
    }
}